
#include "StdAfx.h"
#include "CWADevice.h"
#include "../Common/CommonDef.h"
#include "../Common/CommonFun.h"
#include "../Res/resource.h"
#include "PROCESS.H"
#include "CWAManager.h"
#include "../Frame/GYWalkAidApp.h"

extern volatile BOOL g_bAppQuit;

CWADevice::CWADevice(BLUETOOTH_DEVICE_INFO_EX* pDevInfoEx)
{
	ASSERT(pDevInfoEx);
	memcpy(&m_DevInfoEx, pDevInfoEx, sizeof(BLUETOOTH_DEVICE_INFO_EX));

	m_dwCommVersion = 0;
	m_dwCOMPortNum = 0;
	memset(&m_SPPSvcInfo, 0, sizeof(GENERAL_SERVICE_INFO));
	
	m_dwSPPConnHandle			= NULL;
	m_hCOMPort					= NULL;

	m_dwDataLenToCOM			= 0;
	m_hToCOMDataReady			= NULL;
	m_hToCOMDataEmpty			= NULL;
	memset(m_pDataToCOM, 0, sizeof(m_pDataToCOM));

	m_dwDataLenFromCOM			= 0;
	m_hFromCOMDataReady			= NULL;
	m_hFromCOMDataEmtpy			= NULL;
	memset(m_pDataFromCOM, 0, sizeof(m_pDataFromCOM));

	m_bWAConnected				= FALSE;
	m_nWaitTimeBeforeRead		= 500;
	m_dwMRNCode					= 0;

	m_dwCurrentDataLenFromCOM	= 0;
	m_bDisconnBeforeConn		= FALSE;
	m_bReadGaitStarted			= FALSE;

	m_dwLastError				= WA_ERROR_NONE;

	memset(m_szDevID, 0, sizeof(m_szDevID));

	InitializeCriticalSection(&m_csDataFromCOM);
}

CWADevice::~CWADevice()
{
	DeleteCriticalSection(&m_csDataFromCOM);
}

BOOL CWADevice::InitDev()
{
	UINT unThreadID		=0;
	m_hIOThread			=(HANDLE)_beginthreadex(NULL,0, WAWorkThread, (LPVOID)this, 0, &unThreadID); 

	return TRUE;
}

BOOL CWADevice::UninitDev()
{
	SetEvent(m_hToCOMDataReady);
	WaitForSingleObject(m_hIOThread, 5000);
	CLOSE_HANDLE(m_hIOThread);

	CLOSE_HANDLE(m_hToCOMDataReady);
	CLOSE_HANDLE(m_hToCOMDataEmpty);
	CLOSE_HANDLE(m_hFromCOMDataReady);
	CLOSE_HANDLE(m_hFromCOMDataEmtpy);

	if(m_DevInfoEx.bConnected && m_dwSPPConnHandle)
	{
		CLOSE_HANDLE(m_hCOMPort)

		BT_DisconnectService(m_dwSPPConnHandle);
		m_dwSPPConnHandle = NULL;
		m_DevInfoEx.bConnected = FALSE;
	}

	if(m_DevInfoEx.bPaired)
	{
		BT_UnpairDevice(m_DevInfoEx.address);
		m_DevInfoEx.bPaired = FALSE;
	}

	return TRUE;
}

UINT CWADevice::WAWorkThread(LPVOID lpParam)
{
	CWADevice* pThis = (CWADevice*)lpParam;
	ASSERT(pThis);

	CWAManager* pWAMan = GetApp()->GetWAMan();
	if(!pWAMan)
	{
		ASSERT("Get WAManager Failed!" == NULL);
		GetApp()->GetCustMsgCenter()->PostCustMessage(GYWA_MSG_BLUETOOTH_CONNECT_FAIL, (WPARAM)pThis, NULL);
		return 0;
	}
	
	pWAMan->EnterBTCS();

	if(pThis->m_DevInfoEx.bConnected)
	{
		pThis->m_bDisconnBeforeConn = TRUE;
		pThis->PostProgressMsg(IDS_BT_DISCONNECT_SPP, 15000);

		DWORD dwDisConn = pThis->DisconnectSPPService();
		if(dwDisConn != BTSTATUS_SUCCESS)
		{
			pThis->PostProgressMsg(IDS_BT_DISCONN_FAIL, 15000);
// 			pWAMan->LeaveBTCS();
// 			return 0;
		}

		pThis->m_DevInfoEx.bConnected = FALSE;
		Sleep(1000);
	}

	if(pThis->m_DevInfoEx.bPaired)
	{
		pThis->PostProgressMsg(IDS_BT_DISPAIR, 15000);

		if( pThis->UnpairWADev() != BTSTATUS_SUCCESS )
		{
			pThis->PostProgressMsg(IDS_BT_UNPAIR_FAIL, 15000);
// 			pWAMan->LeaveBTCS();
// 			return 0;
		}

		pThis->m_DevInfoEx.bPaired = FALSE;
		Sleep(3000);
	}

	pThis->PostProgressMsg(IDS_BT_PAIR, 15000);
	if( pThis->PairWADev() != BTSTATUS_SUCCESS )
	{
		pThis->PostProgressMsg(IDS_BT_PAIR_WA_FAIL, 15000);
		GetApp()->GetCustMsgCenter()->PostCustMessage(GYWA_MSG_BLUETOOTH_CONNECT_FAIL, (WPARAM)pThis, NULL);
		
		pWAMan->LeaveBTCS();
		return 0;
	}
	pThis->m_DevInfoEx.bPaired = TRUE;
	Sleep(1000);

	pThis->PostProgressMsg(IDS_BT_CONNECT_SPP, 15000);
	if( pThis->ConnectSPPService() != BTSTATUS_SUCCESS )
	{
		GetApp()->GetCustMsgCenter()->PostCustMessage(GYWA_MSG_BLUETOOTH_CONNECT_FAIL, (WPARAM)pThis, NULL);

		pWAMan->LeaveBTCS();
		return 0;
	}

	pThis->m_DevInfoEx.bConnected = TRUE;
	Sleep(1000);

// 	if( pThis->OpenCOMPort() != BTSTATUS_SUCCESS )
// 	{
// 		pWAMan->LeaveBTCS();
// 		return 0;
// 	}
	
	if(pThis->m_Port.InitPort(pThis, pThis->m_dwCOMPortNum))
	{
		pThis->m_Port.StartMonitoring();
	}
	else
	{
		GetApp()->GetCustMsgCenter()->PostCustMessage(GYWA_MSG_BLUETOOTH_CONNECT_FAIL, (WPARAM)pThis, NULL);

		pWAMan->LeaveBTCS();
		return 0;
	}

	{
		pThis->m_hToCOMDataReady			= CreateEvent(NULL, FALSE, FALSE, NULL);
		pThis->m_hToCOMDataEmpty			= CreateEvent(NULL, FALSE, TRUE, NULL);
		
		pThis->m_hFromCOMDataReady			= CreateEvent(NULL, FALSE, FALSE, NULL);
		pThis->m_hFromCOMDataEmtpy			= CreateEvent(NULL, FALSE, TRUE, NULL);
	}

	//  Bluetooth is busy, sleep to wait it to be idle
	Sleep(3000);

	pWAMan->LeaveBTCS();

	GetApp()->GetCustMsgCenter()->PostCustMessage(GYWA_MSG_BLUETOOTH_DEVICE_CONNECTED, (WPARAM)lpParam, NULL);

	// 	pThis->StartIO();//  waiting for data send/receive
	pThis->m_bDisconnBeforeConn = FALSE;
	return 1;
}


DWORD CWADevice::PairWADev()
{
	WORD wPinCodeLen = strlen(WA_PAIR_PIN_CODE);
	
	BLUETOOTH_DEVICE_INFO devInfo = {0};
	devInfo.dwSize = sizeof(BLUETOOTH_DEVICE_INFO);
	memcpy(devInfo.address, m_DevInfoEx.address, DEVICE_ADDRESS_LENGTH);
	memcpy(devInfo.classOfDevice, m_DevInfoEx.classOfDevice, DEVICE_CLASS_LENGTH);
	memcpy(devInfo.szName, m_DevInfoEx.szName, MAX_DEVICE_NAME_LENGTH);

	DWORD dwResult = BT_PairDevice(&devInfo, wPinCodeLen, (BYTE*)WA_PAIR_PIN_CODE, TRUE, FALSE);

	return dwResult;
}

DWORD CWADevice::UnpairWADev()
{
	DWORD dwResult = BT_UnpairDevice(m_DevInfoEx.address);
	
	return dwResult;
}

DWORD CWADevice::ConnectSPPService()
{
	//  Browse service first
	GENERAL_SERVICE_INFO	pClsidList[MAX_SERVICE_COUNT]={0};
	DWORD					ClsidListLen = MAX_SERVICE_COUNT*sizeof(GENERAL_SERVICE_INFO);

	BLUETOOTH_DEVICE_INFO devInfo = {0};
	devInfo.dwSize = sizeof(BLUETOOTH_DEVICE_INFO);
	memcpy(devInfo.address, m_DevInfoEx.address, DEVICE_ADDRESS_LENGTH);
	memcpy(devInfo.classOfDevice, m_DevInfoEx.classOfDevice, DEVICE_CLASS_LENGTH);
	memcpy(devInfo.szName, m_DevInfoEx.szName, MAX_DEVICE_NAME_LENGTH);
	devInfo.bPaired = TRUE;

	//  if browse specific service, set the 2nd parameter to FALSE and set the ServiceID to pClsidList[0].wServiceClassUuid16 before call this interface
	//  the 1st parameter, devInfo, is a filter to search service.
	DWORD dwReusult = BT_BrowseServices(&devInfo, TRUE, &ClsidListLen, pClsidList);

	int nSPPServiceIndex = -1;
	for(DWORD i=0; i<ClsidListLen/sizeof(GENERAL_SERVICE_INFO); i++)
	{
		if(pClsidList[i].wServiceClassUuid16 == CLS_SERIAL_PORT)
		{
			nSPPServiceIndex = i;
			m_SPPSvcInfo = pClsidList[i];
			break;
		}
	}

	if(nSPPServiceIndex < 0)
	{
		PostProgressMsg(IDS_BT_NO_SPPSERVICE, 15000);
		return BTSTATUS_SERVICE_NOT_EXIST;
	}

	BYTE SPPConnParam[512] = {0};
	((PSPP_CLIENT_PARAM)SPPConnParam)->dwSize = sizeof(SPP_CLIENT_PARAM);

	DWORD dwHandle;
	DWORD dwResult = BT_ConnectService(&devInfo, &pClsidList[nSPPServiceIndex], (BYTE*)&SPPConnParam, &dwHandle);
	if(dwResult == BTSTATUS_SUCCESS)
	{
		m_dwSPPConnHandle	= dwHandle;
		m_dwCOMPortNum		= ((PSPP_CLIENT_PARAM)SPPConnParam)->ucComPort;
	}
	else
	{
		PostProgressMsg(IDS_BT_CONNECT_WA_FAIL, 15000);
		return BTSTATUS_FAIL;
	}

	return dwResult;
}

BOOL CWADevice::DisconnectSPPService()
{
	GENERAL_CONNECT_INFO connList[MAX_CONNECT_COUNT] = {0};
	DWORD dwSize = sizeof(connList);
	DWORD dwResult = BT_EnumConnections(&dwSize,connList);
	
	if(dwResult == BTSTATUS_SUCCESS)
	{
		int nConnCount = dwSize/sizeof(GENERAL_CONNECT_INFO);
		int nIndex = 0;
		for(; nIndex < nConnCount; nIndex++)
		{
			if(memcmp(m_DevInfoEx.address, connList[nIndex].remoteBdAddr, DEVICE_ADDRESS_LENGTH) == 0)
			{
				DWORD dwResult = BT_DisconnectService(connList[nIndex].dwHandle);
				return (dwResult == BTSTATUS_SUCCESS);
			}
		}
	}

	return FALSE;
}

DWORD CWADevice::OpenCOMPort()
{
	TCHAR szCOMPort[64] = {0};
	_stprintf(szCOMPort, _T("\\\\.\\COM%d"), m_dwCOMPortNum);

	m_hCOMPort = CreateFile(szCOMPort, GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
	if (m_hCOMPort == INVALID_HANDLE_VALUE)
	{
		CString strMsg;
		strMsg.Format(GetResString(IDS_BT_OPEN_COM_FAIL), m_dwCOMPortNum);

		GetApp()->GetCustMsgCenter()->PostStringMessage(GYWA_MSG_BLUETOOTH_CONNECT_PROGRESS, strMsg, 1000);
		return BTSTATUS_FAIL;
	}
	
	DCB dcb;
	COMMTIMEOUTS commTimeouts;
	
	GetCommState(m_hCOMPort,&dcb);
	dcb.BaudRate = CBR_9600;
	dcb.Parity = NOPARITY;
	dcb.ByteSize = 8;
	dcb.StopBits = ONESTOPBIT;
	SetCommState(m_hCOMPort,&dcb);
	
	commTimeouts.ReadIntervalTimeout = MAXDWORD;
	commTimeouts.ReadTotalTimeoutConstant = 0;
	commTimeouts.ReadTotalTimeoutMultiplier = 0;
	commTimeouts.WriteTotalTimeoutConstant = 100;
	commTimeouts.WriteTotalTimeoutMultiplier = 10;
	SetCommTimeouts(m_hCOMPort,&commTimeouts);
	PurgeComm(m_hCOMPort, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR );
	SetupComm(m_hCOMPort, 1024, 1024);
	EscapeCommFunction(m_hCOMPort,SETRTS);
	EscapeCommFunction(m_hCOMPort,SETDTR);
	
	return BTSTATUS_SUCCESS;
}

BOOL CWADevice::StartIO()
{
	OVERLAPPED writeo = {0};	
	writeo.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	OVERLAPPED reado = {0};	
	reado.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	for(;;)
	{
		DWORD dwWaitRet = WaitForSingleObject(m_hToCOMDataReady, INFINITE);
		if(dwWaitRet != WAIT_OBJECT_0)
			break;

		if(g_bAppQuit)
			break;

		WriteCOM(&writeo);

		SetEvent(m_hToCOMDataEmpty);

		dwWaitRet = WaitForSingleObject(m_hFromCOMDataEmtpy, INFINITE);
		if(dwWaitRet != WAIT_OBJECT_0)
			break;

		Sleep(m_nWaitTimeBeforeRead);

		ReadCOM(&reado);

		SetEvent(m_hFromCOMDataReady);

		PurgeComm(m_hCOMPort, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
	}

	return TRUE;
}

BOOL CWADevice::WriteCOM(OVERLAPPED* pWriteOverlap)
{
	if(m_dwDataLenToCOM == 0)
		return FALSE;

	DWORD dwErrors;
	COMSTAT comStat;

	DWORD byte_sent = 0;
	DWORD nwritten = 0;

	if ( !WriteFile(m_hCOMPort, m_pDataToCOM, m_dwDataLenToCOM, &byte_sent, pWriteOverlap))
	{
		dwErrors =GetLastError();

		if (dwErrors == ERROR_IO_PENDING)
		{ 
			while(!GetOverlappedResult(m_hCOMPort, pWriteOverlap, &byte_sent, TRUE))
			{
				if ( GetLastError() == ERROR_IO_INCOMPLETE)
				{
					nwritten += byte_sent;
					continue;
				}
				else
				{
					ClearCommError(m_hCOMPort, &dwErrors, &comStat); 
					break;
				}
           	}

			nwritten += byte_sent;			
		}
		else
		{ 
			ClearCommError(m_hCOMPort, &dwErrors, &comStat);
		} 
	}
	else
	{
		nwritten += byte_sent;
	}
	
	return (nwritten == m_dwDataLenToCOM);
}

BOOL CWADevice::ReadCOM(OVERLAPPED* pReadOverlap)
{
	if(m_dwDataLenFromCOM == 0)
		return TRUE;

	COMSTAT comStat;
	DWORD dwErrors;
	DWORD nreturned = 0;

	if( !ReadFile(m_hCOMPort, m_pDataFromCOM, m_dwDataLenFromCOM, &nreturned, pReadOverlap) )
	{
		dwErrors = GetLastError();
		if ( dwErrors== ERROR_IO_PENDING)
		{ 
			DWORD dwRes;
			dwRes = WaitForSingleObject(pReadOverlap->hEvent, 3000);

			switch(dwRes)
			{
			case WAIT_OBJECT_0: 
				GetOverlappedResult(m_hCOMPort, pReadOverlap, &nreturned, FALSE);
				break;

			case WAIT_TIMEOUT:
				break;

			default:					 
				break;
			}
		}
		else
		{ 
			ClearCommError( m_hCOMPort, &dwErrors, &comStat); 
		}
	}
	else
	{
		dwErrors = GetLastError();
	}

	return (nreturned == m_dwDataLenFromCOM);
}

// BOOL CWADevice::ExecuteCmd(BYTE* pInParam, int nInParamLen, BYTE* pOutResult, int nOutResultLen, int nWaitTime)
// {
// 	DWORD dwWaitRet = WaitForSingleObject(m_hToCOMDataEmpty, INFINITE);
// 	if(dwWaitRet != WAIT_OBJECT_0)
// 		return FALSE;
// 	
// 	if(nInParamLen > 0)
// 		memcpy(m_pDataToCOM, pInParam, nInParamLen);
// 	
// 	//  Buf len expect to write to COM
// 	m_dwDataLenToCOM = nInParamLen;
// 	
// 	//  Buf len expect to read from COM
// 	m_dwDataLenFromCOM = nOutResultLen;
// 
// 	//  Time interval before reading
// 	m_nWaitTimeBeforeRead = nWaitTime;
// 
// 	SetEvent(m_hToCOMDataReady);
// 	
// 	//  Wait ReadCOM to complete
// 	dwWaitRet = WaitForSingleObject(m_hFromCOMDataReady, INFINITE);
// 	if(dwWaitRet != WAIT_OBJECT_0)
// 		return FALSE;
// 	
// 	if(m_dwDataLenFromCOM > 0)
// 		memcpy(pOutResult, m_pDataFromCOM, m_dwDataLenFromCOM);
// 
// 	//  Notify to read COM to buf
// 	SetEvent(m_hFromCOMDataEmtpy);
// 	
// 	return TRUE;
// }

BOOL CWADevice::ExecuteCmd(BYTE* pInParam, int nInParamLen, BYTE* pOutResult, int nOutResultLen, int nTimeOut)
{
	if(nInParamLen > 0)
	{
		memcpy(m_pDataToCOM, pInParam, nInParamLen);
		m_Port.WriteHexToPort(m_pDataToCOM, nInParamLen);
	}
	
	//  Buf len expect to write to COM
	m_dwDataLenToCOM = nInParamLen;
	
	//  Buf len expect to read from COM
	m_dwDataLenFromCOM = nOutResultLen;

	int nWaitTimes = 0;
	BOOL bTimeOut = FALSE;
	while(m_dwCurrentDataLenFromCOM < m_dwDataLenFromCOM && IsBlueToothConnect())
	{
		Sleep(10);

		nWaitTimes++;
		if(nWaitTimes * 10 > nTimeOut)
		{
			bTimeOut = TRUE;
			break;
		}
	}

	if(bTimeOut)
	{
		m_dwLastError = WA_ERROR_TIMEOUT;
		return FALSE;
	}

	if(!IsBlueToothConnect())
	{
		m_dwLastError = WA_ERROR_NOTCONNECT;
		return FALSE;
	}

	if(m_dwDataLenFromCOM > 0)
	{
		EnterCriticalSection(&m_csDataFromCOM);

		memcpy(pOutResult, m_pDataFromCOM, m_dwDataLenFromCOM);
		
		m_dwCurrentDataLenFromCOM -= m_dwDataLenFromCOM;

		if(m_dwCurrentDataLenFromCOM > 0)
		{
			BYTE aTmpBuf[sizeof(m_pDataFromCOM)];
			memcpy(aTmpBuf, m_pDataFromCOM + m_dwDataLenFromCOM, m_dwCurrentDataLenFromCOM);
			memcpy(m_pDataFromCOM, aTmpBuf, m_dwCurrentDataLenFromCOM);

#ifdef _DEBUG
			memset(m_pDataFromCOM + m_dwCurrentDataLenFromCOM, 0, sizeof(m_pDataFromCOM) - m_dwCurrentDataLenFromCOM);
#endif

		}
#ifdef _DEBUG
		else
		{
			memset(m_pDataFromCOM, 0, sizeof(m_pDataFromCOM));
		}
#endif

		LeaveCriticalSection(&m_csDataFromCOM);
	}
	
	m_dwLastError = WA_ERROR_SUCCESS;
	return TRUE;
}

BOOL CWADevice::ClearBufFromCOM()
{
	for(int i = 0; i < 5; i++)
	{
		Sleep(100);

		EnterCriticalSection(&m_csDataFromCOM);
		
		if(m_dwCurrentDataLenFromCOM > 0)
			m_dwCurrentDataLenFromCOM = 0;
		
#ifdef _DEBUG
		memset(m_pDataFromCOM, 0, sizeof(m_pDataFromCOM));
#endif
		
		LeaveCriticalSection(&m_csDataFromCOM);
	}

	return TRUE;
}

CString CWADevice::GetAddress()
{
	CString strAddr;
	strAddr.Format(_T("%02X:%02X:%02X:%02X:%02X:%02X"), 
						m_DevInfoEx.address[5],
						m_DevInfoEx.address[4],
						m_DevInfoEx.address[3],
						m_DevInfoEx.address[2],
						m_DevInfoEx.address[1],
						m_DevInfoEx.address[0]);

	return strAddr;
}

BOOL CWADevice::GetAddress(BYTE* pAddres)
{
	NULL_RETURN_FALSE(pAddres);
	memcpy(pAddres, m_DevInfoEx.address, DEVICE_ADDRESS_LENGTH);
	return TRUE;	
}

CString CWADevice::GetName()
{
#ifdef _UNICODE
	TCHAR szwName[MAX_DEVICE_NAME_LENGTH];
	::MultiByteToWideChar(CP_UTF8, NULL, m_DevInfoEx.szName, -1, szwName, MAX_DEVICE_NAME_LENGTH);
	return szwName;
#else
	return m_DevInfoEx.szName;
#endif
}

void CWADevice::PostProgressMsg(DWORD dwStrID, DWORD dwShowDuration)
{
	CString strMsg = GetAddress();
	strMsg += GetResString(dwStrID);

	GetApp()->GetCustMsgCenter()->PostStringMessage(GYWA_MSG_BLUETOOTH_CONNECT_PROGRESS, strMsg, dwShowDuration);
}

DWORD CWADevice::EventCallbackCOM(DWORD dwEvent)
{
	return 0;
}

DWORD CWADevice::AppendCOMByte(BYTE byteData)
{
	EnterCriticalSection(&m_csDataFromCOM);

	if(m_dwCurrentDataLenFromCOM < sizeof(m_pDataFromCOM))
		m_pDataFromCOM[m_dwCurrentDataLenFromCOM++] = byteData;
	
	LeaveCriticalSection(&m_csDataFromCOM);
	
	return 0;
}
